package com.hulk.ratel.manage.commonservice;

import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;


import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.google.common.base.Throwables;

import com.hulk.ratel.common.constant.enums.BussErrCode;
import com.hulk.ratel.common.constant.enums.NotifyLogStatus;
import com.hulk.ratel.common.exception.NotifyRetryRteException;
import com.hulk.ratel.common.exception.SecurityRteException;
import com.hulk.ratel.common.security.ConsumeSecurityWarper;
import com.hulk.ratel.common.util.DateUtil;
import com.hulk.ratel.common.util.SaltGenerator;
import com.hulk.ratel.constant.Constants;
import com.hulk.ratel.manage.bo.AsyncParamRespBO;
import com.hulk.ratel.manage.commoncache.PartnerKeyCache;
import com.hulk.ratel.manage.commoncache.SecurityPartnerCache;
import com.hulk.ratel.manage.commonutil.ContentUtil;
import com.hulk.ratel.manage.transactionservice.RetryNotifyRecordTransactionService;
import com.hulk.ratel.persistence.entity.LogNotify;
import com.hulk.ratel.persistence.entity.LogNotifyRecord;
import com.hulk.ratel.persistence.entity.LogTxnPayRecord;
import com.hulk.ratel.persistence.entity.PartnerKey;
import com.hulk.ratel.persistence.repository.ILogNotifyRecordRepository;
import com.hulk.ratel.persistence.repository.ILogTxnPayRecordRepository;
import com.hulk.ratel.service.dto.AsyncParamRespDTO;
import com.hulk.ratel.service.dto.HeadRespDTO;
import com.hulk.ratel.service.dto.PayNotifyRespDTO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.time.LocalDateTime;
import java.util.Objects;
import java.util.Optional;

/**
 * @Author: hulk
 * @E-mail: 29572320@qq.com
 * @Date: Create in 2018/9/20 下午3:28
 * @Version: 1.0.0
 * @Modify by:
 * Class description
 */
@Slf4j
@AllArgsConstructor
@Component
public class RetryNotifyService {


    private ILogNotifyRecordRepository logNotifyRecordRepository;

    private ILogTxnPayRecordRepository logTxnPayRecordRepository;

    private RetryNotifyRecordTransactionService retryNotifyRecordTransactionService;

    private ContentUtil contentUtil;

    //通知次数
    protected static final int MAX_NOTIFY_COUNT = 5;

    @Autowired
    private PartnerKeyCache partnerKeyCache;

    @Autowired
    private SecurityPartnerCache securityPartnerCache;

    @Retryable(value = {NotifyRetryRteException.class}, maxAttempts = MAX_NOTIFY_COUNT,  backoff = @Backoff(delay = 2000L, maxDelay =90000, multiplier = 2))
    public void doNotify(AsyncParamRespBO bo, LogNotify notify, LogTxnPayRecord record, Long notifyId, String workId) {
        log.info("workId:{},notifyId:{} 交易支付结果,通知前端商户,bo:{} record:{}", workId, notifyId, bo, record);
        try {
            // 下游没有明确响应"SUCCESS"进行重复通知。
            //查询回调次数
            LogNotifyRecord query = new LogNotifyRecord();
            query.setNotifyId(notifyId);
            int times = logNotifyRecordRepository.list(new QueryWrapper<>(query)).size() + 1;

            LogNotifyRecord notifyRecord = initNotifyRecord(notify, record, notifyId, times);

            AsyncParamRespDTO respDTO = getNotifyParam(record,workId);

            String result =  HttpUtil.post(notify.getNotifyUrl(), respDTO.toParamMap());

            log.info("异步通知返回:{}",result);
            if ("SUCCESS".equals(result)) {
                retryNotifyRecordTransactionService.ModifyNotifyRecord(notifyRecord, notify, record, result, NotifyLogStatus.SUCCESS.getValue(), times);
            } else if (StringUtils.isBlank(result)) {
                retryNotifyRecordTransactionService.ModifyNotifyRecord(notifyRecord, notify, record, result, NotifyLogStatus.TIMEOUT.getValue(), times);
                throw new NotifyRetryRteException("", BussErrCode.E_900003);
            } else {
                retryNotifyRecordTransactionService.ModifyNotifyRecord(notifyRecord, notify, record, result, NotifyLogStatus.FAILED.getValue(), times);
                throw new NotifyRetryRteException("", BussErrCode.E_999999);
            }
        }catch (NotifyRetryRteException e){
            throw e;
        } catch (Exception e) {
            log.error("workId:{} 交易支付结果异步通知异常:{}", workId, Throwables.getStackTraceAsString(e));
        }
    }


    @Recover
    public void recover(NotifyRetryRteException ex) {
        log.error("workId:{} 合利宝交易支付结果异步通知前端失败！", ex.getIntTxnSeq(), ex);
    }


    private LogNotifyRecord initNotifyRecord(LogNotify notify, LogTxnPayRecord record, Long notifyId, int times) {
        LogNotifyRecord notifyRecord = new LogNotifyRecord();
        notifyRecord.setId(IdWorker.getId());
        notifyRecord.setNotifyId(notifyId);
        notifyRecord.setNotifyUrl(notify.getNotifyUrl());
        notifyRecord.setPartnerNo(notify.getPartnerNo());
        notifyRecord.setStatus(NotifyLogStatus.INIT.getValue());
        notifyRecord.setTxnCode(notify.getTxnCode());
        notifyRecord.setUpdatedTime(LocalDateTime.now());
        notifyRecord.setTraceId(record.getTraceId());

        notifyRecord.setNotifyTimes(times);
        notifyRecord.setReqTime(DateUtil.getDateyyyyMMddHHmmss());
        notifyRecord.setReqMsg("");
        logNotifyRecordRepository.save(notifyRecord);
        return notifyRecord;
    }

    private AsyncParamRespDTO getNotifyParam(LogTxnPayRecord record,String workId){
        //合作方密钥查询
        PartnerKey partnerKey = Optional.ofNullable(partnerKeyCache.findPartnerKey(Objects.requireNonNull(record.getPartnerNo())))
                .orElseThrow(()->new SecurityRteException(workId,BussErrCode.E_100002));

        String partnerPublicKeyStr = partnerKey.getPartnerPublickey();
        String platformPrivateKeyStr = partnerKey.getPlatformPrivatekey();
        String partnerPublicKeySuffix = partnerKey.getPartnerPublickeySuffix();
        String platformPrivateKeySuffix =partnerKey.getPlatformPrivatekeySuffix();
        String keypathType =  partnerKey.getKeypathType();

        //生成随机AESkey
        String partnerAESKey = SaltGenerator.generate();

        //获取合作方公钥
        PublicKey publicKeyPartner = Optional.ofNullable(securityPartnerCache.findPublicKey(record.getPartnerNo(), partnerPublicKeyStr, partnerPublicKeySuffix, keypathType))
                .orElseThrow(() -> new SecurityRteException(workId, BussErrCode.E_100010));
        //获取平台私钥
        PrivateKey privateKeyPlatform = Optional.ofNullable(securityPartnerCache.findPrivateKey(record.getPartnerNo(),platformPrivateKeyStr,platformPrivateKeySuffix, keypathType))
                .orElseThrow(() -> new SecurityRteException(workId, BussErrCode.E_100020));

        //aes加密报文
        PayNotifyRespDTO payNotifyRespDTO = new PayNotifyRespDTO();
        HeadRespDTO headResp = new HeadRespDTO();
        headResp.setVersion(Constants.VERSION);
        headResp.setCharset(StandardCharsets.UTF_8.name());
        headResp.setPartnerNo(record.getPartnerNo());
        headResp.setPartnerType(record.getPartnerType());
        headResp.setTxnCode(record.getTxnCode());
        headResp.setTraceId(record.getTraceId());
        headResp.setRespDate(DateUtil.getDateyyyyMMdd());
        headResp.setRespTime(DateUtil.getDateyyyyMMddHHmmss());
        headResp.setRespCode(BussErrCode.S_0.getErrCode());
        headResp.setRespMsg(BussErrCode.S_0.getErrDesc());
        payNotifyRespDTO.setHeadRespDTO(headResp);

        //返回报文
        String respJsonStr = JSONUtil.toJsonStr(payNotifyRespDTO);

        //加密
        String encryptData =  ConsumeSecurityWarper.getCipherB64PlainTextByPartnerAESKey(respJsonStr,partnerAESKey);

        //公钥加密AESkey
        String encryptKey = ConsumeSecurityWarper.getCipherB64AESKeyByPlatformPublicKey(partnerAESKey,publicKeyPartner);

        //私钥签名
        String signature = ConsumeSecurityWarper.getSignB64PlainTextByPartnerPrivateKey(respJsonStr , privateKeyPlatform);

        AsyncParamRespDTO asyncParamRespDTO = new AsyncParamRespDTO();
        asyncParamRespDTO.setEncryptData(encryptData);
        asyncParamRespDTO.setEncryptKey(encryptKey);
        asyncParamRespDTO.setSignature(signature);
        asyncParamRespDTO.setTraceId(record.getTraceId());
        return asyncParamRespDTO;
    }


}
